﻿#ifndef ___PCLIB_S_MONGODB___
#define ___PCLIB_S_MONGODB___

/*********************************************************************/
//可选功能：mongodb数据库
/*********************************************************************/
#ifdef PCHAVE_MONGODB

#include "../m_core/m_core.hpp"
#include "s_db.hpp"
extern "C"
{
    #include <mongoc/mongoc.h>
};


/**
*@brief 数据库对象类
*/
class CPCMongoDBDataBase : CPCIDB
{
public:
    CPCMongoDBDataBase()
        : m_Connected(false)
        , m_pMongoClient(NULL)
    {
    }
    virtual ~CPCMongoDBDataBase()
    {
        this->Close();
    }

    //初始化数据库，可以指定依赖的lib位置，mongodb暂不需要
    static CPCResult<> InitDB(const char* dir = NULL, const char* filename = NULL)
    {
        CPCResult<> ret;
        mongoc_init();
        return ret;
    }

    //停止数据库
    static void CleanupDB()
    {
        mongoc_cleanup();
    }

    /**
    *@brief     打开数据库
    *@param     pszHost[in]     主机名
    *@param     usPort[in]      端口
    *@param     pszUser[in]     用户名
    *@param     pszPassword[in] 密码
    *@param     pszDB[in]       数据库名，可以为空，代表admin
    *@param     pszChars[in]    数据库字符集，为空代表不设置
    *@param     nCTimeOut[in]   连接数据库的超时时间，等于0时不设置
    *@param     nRWTimeOut[in]  读写数据库的超时时间，等于0时不设置
    *@return    是否成功
    */
    CPCResult<> Open(const char* pszHost, unsigned short usPort, const char* pszUser, const char* pszPassword, 
        const char* pszDB = NULL, const char* pszChars = NULL,
        unsigned int nCTimeOut = 30000, unsigned int nRWTimeOut = 30000) override
    {
        CPCResult<> result;
        if (pszHost == NULL || pszUser == NULL || pszPassword == NULL )
        {
            return result.SetFail("pszHost == NULL || pszUser == NULL || pszPassword == NULL");
        }
        m_dbName = pszDB ? pszDB : "admin";
        nCTimeOut = (nCTimeOut == 0) ? 10000 : nCTimeOut;       //如果为0，指定为mongodb的默认值10000
        nRWTimeOut = (nRWTimeOut == 0) ? 300000 : nRWTimeOut;   //如果为0，指定为mongodb的默认值300000

        //初始化连接句柄  
        this->Close();
        std::string strUrl = CPCStrUtil::ShortFormat("mongodb://%s:%s@%s:%u/%s?authSource=%s&connecttimeoutms=%u&sockettimeoutms=%u",
            pszUser,pszPassword,pszHost,(unsigned int)usPort,m_dbName.c_str(),m_dbName.c_str(), nCTimeOut, nRWTimeOut);
        auto uri = mongoc_uri_new_with_error(strUrl.c_str(), &m_tmpErr);
        if (!uri) 
        {
            return result.SetFail(m_tmpErr.code, "mongoc_uri_new_with_error FAIL:%s", m_tmpErr.message);
        }

        m_pMongoClient = mongoc_client_new_from_uri(uri);
        mongoc_uri_destroy(uri);
        if (!m_pMongoClient) 
        {
            return result.SetFail("mongoc_client_new_from_uri FAIL");
        }
        return this->TestConnect();
    }

    /**
    *@brief     关闭数据库
    */
    void Close() override
    {
        m_Connected = false;
        if (m_pMongoClient)
        {
            mongoc_client_destroy(m_pMongoClient);
            m_pMongoClient = NULL;
        }
    }

    /**
    *@brief        数据库是否打开
    */
    bool IsOpen() const override
    {
        return (m_Connected && m_pMongoClient != NULL );
    }

    /**
    *@brief     测试连接
    */
    CPCResult<> TestConnect(void)
    {
        CPCResult<> result;
        m_Connected = false;
        if (m_pMongoClient == NULL)
        {
            return result.SetFail("db not open.");
        }
        auto command = BCON_NEW("ping", BCON_INT32(1));
        bson_t reply;
        bool retval = mongoc_client_command_simple(m_pMongoClient, "admin", command, NULL, &reply, &m_tmpErr);
        bson_destroy(&reply);
        bson_destroy(command);
        if (!retval)
        {
            return result.SetFail(m_tmpErr.code, "mongoc_client_command_simple FAIL:%s", m_tmpErr.message);
        }
        m_Connected = true;
        return result;
    }

    /**
    *@brief     增加
    */
    CPCResult<> InsertDoc(const char* tablename, const char* addjson)
    {
        CPCResult<> result;
        if (tablename == NULL || addjson == NULL || addjson[0] == 0)
        {
            return result.SetFail("tablename == NULL || addjson == NULL");
        }
        auto collection = mongoc_client_get_collection(m_pMongoClient, m_dbName.c_str(), tablename);
        PC_DEFER([&collection]() {mongoc_collection_destroy(collection); });

        auto doc = bson_new_from_json((const uint8_t*)addjson, -1, &m_tmpErr);
        if (!doc)
        {
            return result.SetFail(m_tmpErr.code, "bson_new_from_json FAIL:%s", m_tmpErr.message);
        }
        PC_DEFER([&doc]() {bson_destroy(doc); });

        /*
            bson_oid_t oid;
            bson_oid_init(&oid, NULL);
            BSON_APPEND_OID(doc, "_id", &oid);
        */
        
        if (!mongoc_collection_insert_one(collection, doc, NULL, NULL, &m_tmpErr))
        {
            return result.SetFail(m_tmpErr.code, "mongoc_collection_insert_one FAIL:%s", m_tmpErr.message);
        }
        return result;
    }

    /**
    *@brief     删除，limit1表示最多处理1条
    */
    CPCResult<int> DelDoc(const char* tablename, const char* queryjson, bool limit1 = true)
    {
        CPCResult<int> result;
        if (tablename == NULL || queryjson == NULL || queryjson[0] == 0)
        {
            return result.SetFail("tablename == NULL || queryjson == NULL");
        }
        auto collection = mongoc_client_get_collection(m_pMongoClient, m_dbName.c_str(), tablename);
        PC_DEFER([&collection]() {mongoc_collection_destroy(collection); });

        auto query = bson_new_from_json((const uint8_t*)queryjson, -1, &m_tmpErr);
        if (!query)
        {
            return result.SetFail(m_tmpErr.code, "bson_new_from_json FAIL:%s", m_tmpErr.message);
        }
        PC_DEFER([&query]() {bson_destroy(query); });

        bool    bret;
        bson_t  reply;
        PC_DEFER([&reply]() {bson_destroy(&reply); });
        if (limit1)
        {
            bret = mongoc_collection_delete_one(collection, query, NULL, &reply, &m_tmpErr);
        }
        else
        {
            bret = mongoc_collection_delete_many(collection, query, NULL, &reply, &m_tmpErr);
        }
        if (!bret)
        {
            return result.SetFail(m_tmpErr.code, "mongoc_collection_delete_one FAIL:%s", m_tmpErr.message);
        }

        //获取影响的行数
        int cnt = this->BsonGetInt(&reply, "deletedCount", 0);
        return result.SetSucc(cnt);
    }

    /**
    *@brief     更新，limit1表示最多处理1条
    */
    CPCResult<int> UpdateDoc(const char* tablename, const char* queryjson, const char* newjson, bool limit1 = true)
    {
        CPCResult<int> result;
        if (tablename == NULL || queryjson == NULL || queryjson[0] == 0 || newjson == NULL)
        {
            return result.SetFail("tablename == NULL || queryjson == NULL || newjson == NULL");
        }
        auto collection = mongoc_client_get_collection(m_pMongoClient, m_dbName.c_str(), tablename);
        PC_DEFER([&collection]() {mongoc_collection_destroy(collection); });

        auto query = bson_new_from_json((const uint8_t*)queryjson, -1, &m_tmpErr);
        if (!query)
        {
            return result.SetFail(m_tmpErr.code, "bson_new_from_json FAIL:%s", m_tmpErr.message);
        }
        PC_DEFER([&query]() {bson_destroy(query); });

        auto updatecontent = bson_new_from_json((const uint8_t*)newjson, -1, &m_tmpErr);
        if (!updatecontent)
        {
            return result.SetFail(m_tmpErr.code, "bson_new_from_json FAIL:%s", m_tmpErr.message);
        }
        PC_DEFER([&updatecontent]() {bson_destroy(updatecontent); });

        auto updatedoc = bson_new();
        PC_DEFER([&updatedoc]() {bson_destroy(updatedoc); });
        if (!BSON_APPEND_DOCUMENT(updatedoc, "$set", updatecontent))
        {
            return result.SetFail("BSON_APPEND_DOCUMENT FAIL.");
        }

        bool    bret;
        bson_t  reply;
        PC_DEFER([&reply]() {bson_destroy(&reply); });
        if (limit1)
        {
            bret = mongoc_collection_update_one(collection, query, updatedoc, NULL, &reply, &m_tmpErr);
        }
        else
        {
            bret = mongoc_collection_update_many(collection, query, updatedoc, NULL, &reply, &m_tmpErr);
        }
        if (!bret)
        {
            return result.SetFail(m_tmpErr.code, "bson_new_from_json FAIL:%s", m_tmpErr.message);
        }

        //获取影响的行数
        int cnt = this->BsonGetInt(&reply, "modifiedCount", 0);
        return result.SetSucc(cnt);
    }

    /**
    *@brief     查找，查找所有可以用空json：{}
    */
    CPCResult<std::vector<std::string> > QueryDoc(const char* tablename, const char* queryjson)
    {
        CPCResult<std::vector<std::string>> result;
        auto retiter = this->QueryDoc(tablename, queryjson, [&result](const char* recorddoc)->bool
            {
                result.Get().push_back(recorddoc);
                return true;
            });
        if (!retiter)
        {
            return result.SetFail("%s", retiter.ErrDesc());
        }
        return result;
    }

    /**
    *@brief     查找，查找所有可以用空json：{}
    */
    CPCResult<> QueryDoc(const char* tablename, const char* queryjson, std::function<bool(const char*)> recordcallback)
    {
        CPCResult<> result;
        if (tablename == NULL || queryjson == NULL || queryjson[0] == 0)
        {
            return result.SetFail("tablename == NULL || queryjson == NULL");
        }
        auto collection = mongoc_client_get_collection(m_pMongoClient, m_dbName.c_str(), tablename);
        PC_DEFER([&collection]() {mongoc_collection_destroy(collection); });

        auto query = bson_new_from_json((const uint8_t*)queryjson, -1, &m_tmpErr);
        if (!query)
        {
            return result.SetFail(m_tmpErr.code, "bson_new_from_json FAIL:%s", m_tmpErr.message);
        }
        PC_DEFER([&query]() {bson_destroy(query); });

        auto cursor = mongoc_collection_find_with_opts(collection, query, NULL, NULL);
        PC_DEFER([&cursor]() {mongoc_cursor_destroy(cursor); });

        const bson_t* doc;
        while (mongoc_cursor_next(cursor, &doc))
        {
            auto str = bson_as_relaxed_extended_json(doc, NULL);
            bool bcontinue = true;
            if (recordcallback)
            {
                bcontinue = recordcallback(str);
            }
            bson_free(str);
            if (!bcontinue)
            {
                break;
            }
        }
        return result;
    }

    /**
    *@brief     查找个数，查找所有可以用空json：{}
    */
    CPCResult<int64_t> QueryDocCount(const char* tablename, const char* queryjson)
    {
        CPCResult<int64_t> result;
        if (tablename == NULL || queryjson == NULL)
        {
            return result.SetFail("tablename == NULL || queryjson == NULL");
        }
        auto collection = mongoc_client_get_collection(m_pMongoClient, m_dbName.c_str(), tablename);
        PC_DEFER([&collection]() {mongoc_collection_destroy(collection); });

        auto query = bson_new_from_json((const uint8_t*)queryjson, -1, &m_tmpErr);
        if (!query)
        {
            return result.SetFail(m_tmpErr.code, "bson_new_from_json FAIL:%s", m_tmpErr.message);
        }
        PC_DEFER([&query]() {bson_destroy(query); });

        auto cnt = mongoc_collection_count(collection, MONGOC_QUERY_NONE, query, 0, 0, NULL, &m_tmpErr);
        if (cnt < 0)
        {
            return result.SetFail(m_tmpErr.code, "mongoc_collection_count FAIL:%s", m_tmpErr.message);
        }
        return result.SetSucc(cnt);
    }

    /**
    *@brief     执行命令
    */
    CPCResult<std::string> ExecuteCmd(const char* tablename, const char* cmd)
    {
        CPCResult<std::string> result;
        if (tablename == NULL || cmd == NULL)
        {
            return result.SetFail("tablename == NULL || cmd == NULL");
        }
        auto collection = mongoc_client_get_collection(m_pMongoClient, m_dbName.c_str(), tablename);
        PC_DEFER([&collection]() {mongoc_collection_destroy(collection); });

        auto cmdjson = bson_new_from_json((const uint8_t*)cmd, -1, &m_tmpErr);
        if (!cmdjson)
        {
            return result.SetFail(m_tmpErr.code, "bson_new_from_json FAIL:%s", m_tmpErr.message);
        }
        PC_DEFER([&cmdjson]() {bson_destroy(cmdjson); });

        bson_t reply;
        if (!mongoc_collection_command_simple(collection, cmdjson, NULL, &reply, &m_tmpErr)) 
        {
            result.SetFail(m_tmpErr.code, "mongoc_collection_command_simple FAIL:%s", m_tmpErr.message);
        }
        else 
        {
            auto str = bson_as_relaxed_extended_json(&reply, NULL);
            result.SetSucc(str);
            bson_free(str);
        }
        bson_destroy(&reply);
        return result;
    }

    

private:

    //从bson里面读取一个int，失败返回defaultint
    static int BsonGetInt(bson_t* reply, const char* key, int defaultint = 0)
    {
        bson_iter_t iter;
        if (bson_iter_init_find(&iter, reply, key) )
        {
            if (BSON_ITER_HOLDS_INT32(&iter))
            {
                return bson_iter_int32(&iter);
            }
            else if (BSON_ITER_HOLDS_INT64(&iter))
            {
                return (int)bson_iter_int64(&iter);
            }
        }
        return defaultint;
    }
    
    
    

private:
    bool             m_Connected;
    mongoc_client_t* m_pMongoClient;
    bson_error_t     m_tmpErr;
    std::string      m_dbName;
};


#endif      //PCHAVE_MONGODB 

#endif      //___PCLIB_S_MONGODB___